@file:Suppress("UNCHECKED_CAST", "USELESS_CAST", "INAPPLICABLE_JVM_NAME")
package uts.sdk.modules.utsUdp;
import io.dcloud.uniapp.*;
import io.dcloud.uniapp.extapi.*;
import io.dcloud.uts.*;
import io.dcloud.uts.Map;
import io.dcloud.uts.Set;
import io.dcloud.uts.UTSAndroid;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import kotlin.ByteArray;
import kotlin.String;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.Deferred;
import kotlinx.coroutines.Dispatchers;
import kotlinx.coroutines.async;
open class UDPData {
    open var address: InetAddress;
    open var host: String;
    open var port: Int;
    open var size: Int;
    open var msg: String;
    constructor(address: InetAddress, host: String, port: Int, size: Int, msg: String){
        this.address = address;
        this.host = host;
        this.port = port;
        this.size = size;
        this.msg = msg;
    }
}
open class UDPClientSendOption (
    @JsonNotNull
    open var host: String,
    @JsonNotNull
    open var port: Number,
    @JsonNotNull
    open var msg: String,
    open var enableRecive: Boolean? = null,
    open var receiveTimeout: Number? = null,
    open var receiveByteSize: Number? = null,
    open var onceReceive: ((data: UDPData) -> Unit)? = null,
    open var onceReceiveTimeout: (() -> Unit)? = null,
    open var onError: ((error: String) -> Unit)? = null,
    open var onCompleted: (() -> Unit)? = null,
) : UTSObject()
open class UDPThread : Thread {
    private var callback: (() -> Unit);
    constructor(callback: () -> Unit) : super() {
        this.callback = callback;
    }
    override fun run(): Unit {
        this.callback();
    }
}
open class UDPServer {
    private var port: Int;
    private var byteSize: Int;
    private var socket: DatagramSocket? = null;
    private var isListener = false;
    constructor(port: Int, byteSize: Number = 1024){
        this.port = port;
        this.byteSize = byteSize.toInt();
    }
    open fun listener(receive: (data: UDPData) -> Unit, error: ((message: String) -> Unit)?) {
        if (this.socket != null || this.isListener) {
            return;
        }
        UDPThread(fun(){
            val socket = DatagramSocket(null);
            this.socket = socket;
            try {
                socket.bind(InetSocketAddress(this.port));
                val buff = ByteArray(this.byteSize);
                val packet = DatagramPacket(buff, buff.size);
                this.isListener = true;
                while(true){
                    this.socket?.receive(packet);
                    val msg = String(buff, 0, packet.getLength());
                    val data = UDPData(packet.getAddress(), packet.getAddress().getHostName(), packet.getPort().toInt(), msg.length, msg);
                    receive(data);
                }
            }
             catch (e: Throwable) {
                if (!socket.isClosed()) {
                    error?.invoke(e.message.toString());
                }
                this.stop();
            }
        }
        ).start();
    }
    open fun send(msg: String, host: String, port: Int) {
        UDPThread(fun(){
            val bytes = msg.toByteArray();
            val address = InetAddress.getByName(host);
            this.socket?.send(DatagramPacket(bytes, bytes.size, address, port));
        }
        ).start();
    }
    open fun stop() {
        this.isListener = false;
        this.socket?.close();
        this.socket = null;
    }
}
open class UDPClient {
    companion object {
        fun send(option: UDPClientSendOption) {
            UDPThread(fun(){
                val content = option.msg.toByteArray();
                val socket = DatagramSocket();
                var receiveTimeout = option.receiveTimeout;
                var receiveByteSize = option.receiveByteSize;
                var enableRecive = option.enableRecive;
                if (receiveTimeout == null) {
                    receiveTimeout = 5000;
                }
                if (receiveByteSize == null) {
                    receiveByteSize = 1024;
                }
                if (enableRecive == null) {
                    enableRecive = false;
                }
                try {
                    socket.send(DatagramPacket(content, content.size, InetAddress.getByName(option.host), option.port.toInt()));
                    if (enableRecive) {
                        val buff = ByteArray(receiveByteSize.toInt());
                        val packet = DatagramPacket(buff, buff.size);
                        socket.setSoTimeout(receiveTimeout.toInt());
                        socket.receive(packet);
                        val receiveMsg = String(buff, 0, packet.getLength());
                        val data = UDPData(packet.getAddress(), packet.getAddress().getHostName(), packet.getPort().toInt(), receiveMsg.length, receiveMsg);
                        option.onceReceive?.invoke(data);
                    }
                }
                 catch (e: Throwable) {
                    if (e is SocketTimeoutException) {
                        option.onceReceiveTimeout?.invoke();
                    } else {
                        option.onError?.invoke(e.message.toString());
                    }
                }
                 finally{
                    socket.close();
                    option.onCompleted?.invoke();
                }
            }
            ).start();
        }
    }
}
open class UDPDataByJs : UDPData {
    constructor(address: InetAddress, host: String, port: Int, size: Int, msg: String) : super(address, host, port, size, msg) {}
}
open class UDPClientSendOptionJSONObject : UTSJSONObject() {
    open lateinit var host: String;
    open lateinit var port: Number;
    open lateinit var msg: String;
    open var enableRecive: Boolean? = null;
    open var receiveTimeout: Number? = null;
    open var receiveByteSize: Number? = null;
    open var onceReceive: UTSCallback? = null;
    open var onceReceiveTimeout: UTSCallback? = null;
    open var onError: UTSCallback? = null;
    open var onCompleted: UTSCallback? = null;
}
open class UDPServerByJs : UDPServer {
    constructor(port: Int, byteSize: Number = 1024) : super(port, byteSize) {}
    open fun listenerByJs(receive: UTSCallback, error: UTSCallback?) {
        return listener(fun(data: UDPData){
            receive(data);
        }
        , fun(message: String){
            error?.invoke(message);
        }
        );
    }
    open fun sendByJs(msg: String, host: String, port: Int) {
        return send(msg, host, port);
    }
    open fun stopByJs() {
        return stop();
    }
}
open class UDPClientByJs : UDPClient {
    constructor() : super() {}
    companion object {
        fun sendByJs(option: UDPClientSendOptionJSONObject) {
            return send(UDPClientSendOption(host = option.host, port = option.port, msg = option.msg, enableRecive = option.enableRecive, receiveTimeout = option.receiveTimeout, receiveByteSize = option.receiveByteSize, onceReceive = fun(data: UDPData): Unit {
                option.onceReceive?.invoke(data);
            }
            , onceReceiveTimeout = fun(): Unit {
                option.onceReceiveTimeout?.invoke();
            }
            , onError = fun(error: String): Unit {
                option.onError?.invoke(error);
            }
            , onCompleted = fun(): Unit {
                option.onCompleted?.invoke();
            }
            ));
        }
    }
}
